Skip to content

Release v2.1.0#19

Merged
SimplyLiz merged 16 commits intomainfrom
release/v2.1.0
Apr 15, 2026
Merged

Release v2.1.0#19
SimplyLiz merged 16 commits intomainfrom
release/v2.1.0

Conversation

@SimplyLiz
Copy link
Copy Markdown
Contributor

Release v2.1.0 — Streaming context + forward-compat primitives

Merges 13 commits from develop into main, plus the necessary conflict resolution for the lip → lip-core crate rename that landed on main in v2.0.1 (#8). This is a real merge commit (not squash) so per-feature PR history (#11, #12, #13, #14, #15, #16) is preserved on main.

Conflicts resolved

  • Cargo.toml — kept main's richer crates.io metadata (rust-version, documentation, nyxcore.cloud email, lip-sigma.vercel.app homepage) + bumped version to 2.1.0.
  • bindings/rust/benches/framing.rs, bindings/rust/tests/integration.rs — ported develop's new ErrorCode imports + expanded tests to lip_core:: namespace (previously lip::).
  • tools/lip-cli/Cargo.toml, tools/lip-registry/Cargo.toml — bumped inner-workspace lip-core dep version 2.0.1 → 2.1.0.
  • CHANGELOG.md — placed 2.1.0 above the existing 2.0.1 entry.

Incidental test fix

Incidental CI stability fix

  • daemon::watcher::tests::watcher_detects_file_change replaced a fixed 3s sleep with a 15s poll. The fixed sleep was marginal on slow CI runners and locally flaky. No behavior change — same assertion, just non-fragile timing.

Highlights (full detail in CHANGELOG.md)

  • Streaming: StreamContext wire message (§9.2), token-budgeted, EndStream terminator, relevance tiers. protocol_version bumped 1 → 2. New lip stream-context subcommand.
  • New primitives: EmbedText; RegisterTier3Source + IndexStatusResult.tier3_sources for SCIP import provenance; lip import --no-provenance opt-out.
  • Forward-compat: HandshakeResult.supported_messages; graceful UnknownMessage reply keeping socket open; ServerMessage::Error { code: ErrorCode } with 8 distinct codes (UnknownModel wired via HTTP error classification in daemon/embedding.rs::classify_http_error).
  • Drift guard: ClientMessage::variant_tag + paired exhaustive-match test prevents capability-list drift.
  • Fixed: QueryExpansion honors caller's model pin; handler contract pinned at db layer via query_expansion_terms_rejects_cross_model_scoring.

Tests

  • cargo test --lib → 277 passed
  • cargo test --test integration → 12 passed
  • cargo test full suite → all green

Test plan

  • cargo test --lib
  • cargo test --test integration
  • cargo check --all-targets clean
  • CI green against release branch

🤖 Generated with Claude Code

SimplyLiz and others added 16 commits April 13, 2026 18:16
- Replace docs/user/*.md links with lip-sigma.vercel.app equivalents
- Convert docs/index.astro to index.mdx so markdown renders correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements LIP 2.1.0 proposal: callers stream symbols ranked by relevance
to a cursor position and stop reading when their prompt budget is full,
instead of fetching top-k and locally truncating.

- ClientMessage::StreamContext { file_uri, cursor_position, max_tokens, model? }
- ServerMessage::SymbolInfo { symbol_info, relevance_score, token_cost }
- ServerMessage::EndStream { reason, emitted, total_candidates, error? }
  with EndStreamReason = budget_reached | exhausted | error
- Daemon writes one frame at a time; back-pressure throttles ranking,
  BrokenPipe aborts the walk on client disconnect.
- Relevance order (spec §2.3): cursor symbol → callers (blast-radius CPG)
  → callees → related types.
- Conservative chars÷4 + 8 token-cost estimate per spec §2.4.
- protocol_version bumped 1 → 2 in HandshakeResult.
- New `lip stream-context <uri> <line:col> --max-tokens N` CLI subcommand.
- Integration tests cover zero-budget, cursor out-of-range, and v2 handshake.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat: v2.1.0 — stream_context (token-budgeted RAG context streaming)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes the gap left by EmbeddingBatch (URI-only) and QueryNearestByText
(embeds internally but discards the vector). Callers re-ranking with
their own scoring — centroid arithmetic, federated nearest-neighbour,
lexical-then-semantic re-rank — get the embedding directly instead of
having to build a centroid out of nearest-neighbour seeds.

- ClientMessage::EmbedText { text, model? }
- ServerMessage::EmbedTextResult { vector, embedding_model }
- Handler reuses EmbeddingClient::embed_texts; returns the model
  actually used (after any override).
- Rejected from Batch / BatchQuery (async HTTP, like other embedding ops).
- Round-trip + integration tests; CHANGELOG entry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat: embed_text — unary text-to-vector embedding
- HandshakeResult gains supported_messages: Vec<String> listing every
  ClientMessage type tag this daemon understands. Clients can probe for
  individual features (stream_context, embed_text) without comparing
  protocol_version integers. Field is #[serde(default)] so older daemons
  yield an empty vector.
- ServerMessage::UnknownMessage { message_type, supported } — when the
  client sends a well-formed envelope whose type tag is unknown, the
  daemon replies with UnknownMessage (carrying the tag + supported list)
  and keeps the socket open, instead of closing after a generic parse
  Error. Lets forward-compatible clients downgrade gracefully.
- Added integration tests for both behaviors; run loop inspects the
  parse error text for "unknown variant" to distinguish recoverable
  unknown-tag cases from malformed JSON.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat(2.1): capability discovery + graceful unknown-variant handling
- Added `ErrorCode` enum (unknown_message_type, unknown_model,
  cursor_out_of_range, index_locked, internal) and `code: ErrorCode`
  field on `ServerMessage::Error`. Clients can now branch on a stable
  category instead of string-matching `message`.
- `#[serde(default)]` keeps the wire backwards-compatible: older
  daemons deserialize as `ErrorCode::Internal`.
- Classified all 10 "embedding endpoint not configured / no cached
  embedding for URI" sites as `unknown_model`; everything else
  defaults to `internal`. Integration test asserts the UnknownModel
  code on embed_text when LIP_EMBEDDING_URL is unset.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…odel (#12)

QueryExpansion embedded the query with the requested model but then
ranked candidates across all stored symbol embeddings regardless of
which model produced them. Cross-model cosine scores are meaningless,
so the returned "expansion terms" were noise whenever the index held
mixed-model vectors.

- Added `model_filter: Option<&str>` parameter to
  `LipDatabase::nearest_symbol_by_vector`. When `Some(m)`, only
  symbols whose stored embedding was produced by `m` are scored.
- QueryExpansion handler captures the actual model returned by
  `embed_texts` (previously discarded) and passes it as the filter,
  guaranteeing query-vector and candidate-vectors share a model.
- SimilarSymbols (resolves from a URI's own cached embedding) keeps
  the old unfiltered behavior by passing `None`.
- Consolidated v2.1 CHANGELOG into one heading per user request.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
#13)

Pre-release polish for 2.1.0. Once the tag ships, ErrorCode becomes
load-bearing with its current meaning — splitting the conflations now
is free; splitting them later is a breaking contract change.

Error codes split:
- `EmbeddingNotConfigured` — daemon has no LIP_EMBEDDING_URL
- `NoEmbedding`            — URI not yet embedded; retry after batch
- `InvalidRequest`         — client-side misuse (nested Batch,
                             StreamContext inside Batch)

Previously all three collapsed into `UnknownModel` / `Internal`, so
clients could not tell "retry after embedding" from "model broken"
from "my request shape is wrong."

Drift guard:
- `ClientMessage::variant_tag` is an exhaustive-match method; adding
  a new variant breaks compilation until acknowledged. The paired
  `supported_messages_covers_all_variants` test then enforces that
  the new tag also appears in the handshake capability list.

Also:
- benches/framing.rs lost the `code` field on ServerMessage::Error
  since it was added to the struct; repaired so `cargo bench` compiles.
- CHANGELOG 2.1.0 reformatted with v2.0-style subsection headers and
  corrected to reflect the new, non-conflated error codes.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes the silent-staleness footgun on Tier 3 (SCIP) imports. The
daemon previously had no way to report *what* produced imported
symbols or *when* they landed, so stale Tier 3 data returned
confidently-wrong results with no observable signal.

New wire surface (additive, backwards-compatible):
- `Tier3Source { source_id, tool_name, tool_version, project_root,
  imported_at_ms }` — provenance record.
- `ClientMessage::RegisterTier3Source { source }` — idempotent
  registration; re-registering the same `source_id` refreshes
  `imported_at_ms` in place. Ack'd with `DeltaAck`. Rejected inside
  `BatchQuery` (mutation).
- `IndexStatusResult.tier3_sources: Vec<Tier3Source>` — sorted by
  `source_id`. `#[serde(default)]`, so older daemons yield an empty
  list to newer clients.

CLI wiring:
- `lip import --push-to-daemon` now extracts SCIP `Metadata.tool_info`
  + `project_root` and registers before streaming deltas. `source_id`
  = `sha256(tool_name + ":" + project_root)` so re-imports of the
  same source collapse to one entry.
- `lip-cli mcp` index-status text output now surfaces the tier3 list.

The daemon deliberately does no staleness detection. This is a
provenance primitive, not a policy: clients decide what "stale"
means (time threshold, commit drift vs. HEAD, etc.). The 2.1 goal
is visibility, not auto-reindex.

Tests: +4 (round-trip, missing-field fallback, sort order, re-reg
overwrite). Drift-guard sample list updated for the new variant.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ephemeral or test imports can skip `RegisterTier3Source` so they
don't pollute a long-lived daemon's `tier3_sources` list. No effect
on the default EventStream-JSON output path, where provenance was
never sent anyway.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
)

Ship-blockers flagged in the 2.1.0 review.

**1. ErrorCode::UnknownModel is now actually reachable.**

The embedding HTTP client previously collapsed every endpoint error
into a generic anyhow::Error, forcing the session layer to tag all
HTTP failures as ErrorCode::Internal. The UnknownModel code was
defined and documented but unreachable — exactly the conflation the
error-code split was meant to end.

New `EmbedError` enum with four variants: UnknownModel, Transport,
Protocol, Http. Classification lives in
`daemon/embedding.rs::classify_http_error`:

- 404 → UnknownModel (OpenAI/Ollama/most compatible backends)
- 4xx with `model_not_found`, `"unknown model"`, or
  `"model … not found/invalid/unsupported"` in the body → UnknownModel
- Auth (401), rate-limit (429), and 5xx → Http → Internal at the wire
- Parse / count-mismatch → Protocol → Internal

Conservative on purpose — mentioning "model" alone (e.g. in a token
payload or a parameter error) does not flip it to UnknownModel.

Session.rs routes classification via `embed_error_response(e)` at
all 10 call sites, replacing the hand-rolled `format!("embedding
failed: {e}")` + `ErrorCode::Internal` blocks.

**2. QueryExpansion handler contract pinned by a db-level test.**

Extracted the post-embedding ranking into
`LipDatabase::query_expansion_terms(query_vec, actual_model, top_k)`.
The session handler is now a single call, and the db method is
covered by `query_expansion_terms_rejects_cross_model_scoring` which
puts a matching-model symbol and a cross-model symbol at identical
vectors and asserts only the matching-model term appears. A
regression that silently drops the model filter would flip the
assertion.

Tests: +7 classifier cases, +1 handler-contract test (284 lib tests
pass; was 276).

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts:
#	CHANGELOG.md
#	Cargo.lock
#	Cargo.toml
#	bindings/rust/benches/framing.rs
#	bindings/rust/tests/integration.rs
FSEvents / inotify latency varies under load; a fixed 3s sleep was
marginal on slow runners. Poll up to 15s with 100ms ticks instead —
fast in the happy case, robust when the runner is loaded.
@SimplyLiz SimplyLiz merged commit 68f22d1 into main Apr 15, 2026
@SimplyLiz SimplyLiz deleted the release/v2.1.0 branch April 15, 2026 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant